#include "StationaryWashingDevice.h"
#include "Direction.h"
#include "EventAggregator.h"
#include "TrayStatus.h"

#define STANDARD_DELAY 50
//COMMENT OUT IN VISUAL STUDIO
//int main()
//{
//	StationaryWashingDevice *ASSWD = new StationaryWashingDevice();
//	ASSWD->run();
//	return 0;
//}

StationaryWashingDevice::StationaryWashingDevice()
{
	Serial.println("START ASSWD");
	delay(500);
	start = false;
	selectedProgram = -1; //values from 0 to 2.

	
	Program *innerProgram;
	WashingProgram *programA = programs[0] = new WashingProgram(); //ARE THE POINTERS RIGHT?
	programA->name = "Program A";
	programA->tray1NeedsToBeFilled = true;
	programA->tray2NeedsToBeFilled = true;
	programA->price = 250;
	programA->steps[0].cmd = SETWATERLEVEL;
	programA->steps[0].argument[0]->ui = 66;
	programA->steps[1].cmd = WAITFORCORRECTWATERLEVEL;
	programA->steps[2].cmd = SETTEMPERATURE;
	programA->steps[2].argument[0]->ui = 50;
	programA->steps[3].cmd = WAITFORCORRECTTEMPERATURE;
	programA->steps[4].cmd = FLUSHTRAY;
	programA->steps[4].argument[0]->ui = 0;
	programA->steps[5].cmd = MOTORSTART;
	programA->steps[5].argument[0]->ui = 66;
	programA->steps[5].argument[1]->d = CLOCKWISE;
	programA->steps[6].cmd = WAIT;
	programA->steps[6].argument[0]->ui = 60;//seconds
	programA->steps[7].cmd = MOTORSTOP;
	programA->steps[8].cmd = MOTORSTART;
	programA->steps[8].argument[0]->ui = 66;
	programA->steps[8].argument[1]->d = ACLOCKWISE;
	programA->steps[9].cmd = WAIT;
	programA->steps[9].argument[0]->ui = 60;
	programA->steps[10].cmd = SETWATERLEVEL;
	programA->steps[10].argument[0]->ui = 0;
	programA->steps[11].cmd = WAITFORCORRECTWATERLEVEL;
	
	//MAIN WASH
	programA->steps[12].cmd = SETWATERLEVEL;
	programA->steps[12].argument[0]->ui = 100;
	programA->steps[13].cmd = WAITFORCORRECTWATERLEVEL;
	programA->steps[14].cmd = SETTEMPERATURE;
	programA->steps[14].argument[0]->ui = 100;
	programA->steps[15].cmd = WAITFORCORRECTTEMPERATURE;
	programA->steps[16].cmd = FLUSHTRAY;
	programA->steps[16].argument[0]->ui = 1;
	programA->steps[17].cmd = REPEAT;
	programA->steps[17].argument[0]->ui = 4; //repeat 4 times;
	innerProgram = programA->steps[17].argument[1]->program = new Program();
	innerProgram->steps[0].cmd = MOTORSTART;
	innerProgram->steps[0].argument[0]->ui = 66;
	innerProgram->steps[0].argument[1]->d = CLOCKWISE;
	innerProgram->steps[1].cmd = WAIT;
	innerProgram->steps[1].argument[0]->ui = 60;
	innerProgram->steps[2].cmd = MOTORSTOP;
	innerProgram->steps[3].cmd = MOTORSTART;
	innerProgram->steps[3].argument[0]->ui = 66;
	innerProgram->steps[3].argument[1]->d = ACLOCKWISE;
	innerProgram->steps[4].cmd = WAIT;
	innerProgram->steps[4].argument[0]->ui = 60;
	programA->steps[18].cmd = SETWATERLEVEL;
	programA->steps[18].argument[0]->ui = 0;
	programA->steps[19].cmd = WAITFORCORRECTWATERLEVEL;
	
	//CENTRIFUGATION
	programA->steps[20].cmd = SETTEMPERATURE;
	programA->steps[20].argument[0]->ui = 0;
	programA->steps[21].cmd = WAITFORCORRECTTEMPERATURE;
	programA->steps[22].cmd = REPEAT;
	programA->steps[22].argument[0]->ui = 2;//repeat 2 times.
	innerProgram = programA->steps[22].argument[1]->program = new Program();
	innerProgram->steps[0].cmd = MOTORSTART;
	innerProgram->steps[0].argument[0]->ui = 100;
	innerProgram->steps[0].argument[1]->d = CLOCKWISE;
	innerProgram->steps[1].cmd = WAIT;
	innerProgram->steps[1].argument[0]->ui = 30;
	innerProgram->steps[2].cmd = MOTORSTOP;
	innerProgram->steps[3].cmd = MOTORSTART;
	innerProgram->steps[3].argument[0]->ui = 100;
	innerProgram->steps[3].argument[1]->d = ACLOCKWISE;
	innerProgram->steps[4].cmd = WAIT;
	innerProgram->steps[4].argument[0]->ui = 30;
	programA->steps[23].cmd = STOP;
	
	onStartPressedHandlerEnabled = false;
	onSelectProgramHandlerEnabled = false;

	

	HAL = new HardwareAbstractionInteractionProtocolLayer();
	heater = new AtomicAtomShakingDeviceController(HAL);
	motor = new CircularRotationDeviceController(HAL);
	buttons = new HeavyDutySuperCollidingSuperButtonDeviceController(HAL);
	soap = new HydrofobicAntiStainDeviceController(HAL);
	soap->setTray(0,new HydrofobicAntiStainCompartiment(HAL));
	soap->setTray(1,new HydrofobicAntiStainCompartiment(HAL));
	water = new LiquidDistributionChannelingDeviceController(HAL);
	money = new MonetaryValuedCircularMetalObjectDeviceController(HAL);
	door = new UserInteractionGateDeviceController(HAL);
	HAL->ignoreTheDIP(buttons,door,heater,money,soap->getTray(0),soap->getTray(1),this,water);
	
	HAL->initialize();
}


bool StationaryWashingDevice::onStartPressed()
{
	//PRECONDITION
	if(!onStartPressedHandlerEnabled) return start;

	if(selectedProgram==-1) {Serial.println("No Program selected");return start;}
	if(money->getAmountEntered()==0) {Serial.println("No money");return start;}
	
	if(money->getAmountEntered()>=programs[selectedProgram]->price)
	{
		//check waterpressure
		if(!water->getWaterPressureHigh()) {Serial.println("Please turn on water pressure.");return start;}
		//check soap for program
		if (programs[selectedProgram]->tray1NeedsToBeFilled && soap->getTrayStatus(0)==EMPTY) {Serial.println("Tray 1 should be filled");return start;}
		if (programs[selectedProgram]->tray2NeedsToBeFilled && soap->getTrayStatus(1)==EMPTY) {Serial.println("Tray 2 should be filled");return start;};
		//check door closed
		if (door->getStatus() == OPEN) {Serial.println("Door must be closed");return start;};
		//give back resting amount if existing.
		money->giveBackRest(money->getAmountEntered()-programs[selectedProgram]->price);
		
		//remove start handler
		onStartPressedHandlerEnabled = false;
		//remove program handler
		onSelectProgramHandlerEnabled = false;
		//start = true;
		start = true;
	}
	else {Serial.print("Too little entered.");Serial.println(money->getAmountEntered());return start;}

	return start;
}


void StationaryWashingDevice::onSelectProgram()
{
	//PRECONDITION
	if(!onSelectProgramHandlerEnabled) return;

	selectedProgram++;
	selectedProgram%=3;
	//Serial.println("CURR PROG: ");
	Serial.println(selectedProgram);
}

void StationaryWashingDevice::run()
{
	for(;;)
	{
		//ONE CYCLE IS ONE 'MONEY ENTRY, PROGRAM SELECTION AND PROGRAM EXECUTION'.
		//in this cycle, it is normal that some event handlers get added AND REMOVED. Removing it is the proper way of stopping execution of a generichandler.
		
		//onStartPressedHandler = EventHandler::construct<StationaryWashingDevice, &StationaryWashingDevice::onStartPressed>(this);
		//buttons->onStartPressed->addHandler(onStartPressedHandler);

		onStartPressedHandlerEnabled = true;

		//onProgramSelectHandler = EventHandler::construct<StationaryWashingDevice, &StationaryWashingDevice::onSelectProgram>(this);
		//buttons->onSelectProgram->addHandler(onProgramSelectHandler);
		
		onSelectProgramHandlerEnabled = true;

		while(!start) {delay(STANDARD_DELAY);}

		
		//lock door
		door->lock();
		//lock soap tray
		soap->lockTrays();
		//executie programma
		Serial.print("Executing program ");
		Serial.print(selectedProgram);
		executeSteps(programs[selectedProgram]);
		//unlock door
		door->unlock();
		//unlock soap tray
		soap->unlockTrays();

		//motor->start(66,(Direction) 0);
		//water->setWaterLevel(66);
		//heater->setTemperature(100);
		//while(!(heater->isCorrectTemp())) {delay(100);}//REPLACE WITH EVENT. event sets local variable. then poll for variable.
		//Serial.println("Heater at temperature!");
		//motor->stop();
		//Serial.println("Motor stopped!");
		while(true) {}
	}
}

void StationaryWashingDevice::executeSteps(Program *program)
{
	
	ProgramStep currentStep;
	for(unsigned int count=0;;count++)
	{
		currentStep = program->steps[count];
		switch(currentStep.cmd)
		{
			case STOP:
				return;
			case SETWATERLEVEL:
				water->setWaterLevel(currentStep.argument[0]->ui);
				break;
			case WAITFORCORRECTWATERLEVEL:
				//Serial.print("CURR LEVEL:");
				//Serial.println(water->getIsAtLevel());
				while (!(water->getIsAtLevel())) {delay(10);}
				//Serial.print("LEVEL REACHED");
				break;
			case SETTEMPERATURE:
				heater->setTemperature(currentStep.argument[0]->ui);
				break;
			case WAITFORCORRECTTEMPERATURE:
				while (!(heater->getIsCorrectTemp())) {delay(STANDARD_DELAY);}
				break;
			case FLUSHTRAY:
				soap->flushTray(currentStep.argument[0]->ui);
				break;
			case MOTORSTART:
				if(water->wantedWaterLevel==0 && currentStep.argument[0]->ui==100)
				{
					//WHEN SETWATERLEVEL=0 FOLLOWED BY STARTMOTOR = 100, WAIT FOR WATERLEVEL=0;
					while (!(water->getIsAtLevel())) {delay(STANDARD_DELAY);}
				}
				motor->start(currentStep.argument[0]->ui,currentStep.argument[1]->d);
				break;
			case MOTORSTOP:
				motor->stop();
				break;
			case WAIT:
				delay(1000*currentStep.argument[0]->ui);
				break;
			case REPEAT:
				int repeats = currentStep.argument[0]->ui;
				while(repeats-->=1)
				{
					executeSteps(currentStep.argument[1]->program); //IM SUCH A BADASS.
				}
				break;			
				
		}
	}
}
